[id].vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. <!--
  2. * @Author: LiZhiWei
  3. * @Date: 2026-01-22 14:08:24
  4. * @LastEditors: LiZhiWei
  5. * @LastEditTime: 2026-01-22 17:57:25
  6. * @Description: 解决方案详情页
  7. -->
  8. <template>
  9. <div v-if="solution" class="solution-page">
  10. <!-- Hero Section -->
  11. <section class="solution-hero">
  12. <div
  13. class="landing-container flex flex-col items-center justify-center h-full text-center lt-sm:px-32px"
  14. >
  15. <div class="hero-title pf-sc-semibold">{{ solution.title }}</div>
  16. <div class="hero-subtitle pf-sc-semibold">{{ solution.subTitle }}</div>
  17. <div class="hero-desc pf-sc-regular">{{ solution.description }}</div>
  18. <button class="hero-btn btn-primary pf-sc-semibold" @click="openConsultation">
  19. 立即免费咨询
  20. </button>
  21. </div>
  22. </section>
  23. <!-- Core Pain Points -->
  24. <section class="section-container bg-white">
  25. <div class="landing-container lt-sm:px-32px">
  26. <div class="section-title pf-sc-semibold">{{ solution.corePoints.title }}</div>
  27. <div class="grid grid-cols-3 gap-25px lt-sm:grid-cols-1">
  28. <div
  29. v-for="(item, index) in solution.corePoints.points"
  30. :key="index"
  31. class="pain-card group"
  32. >
  33. <i :class="[item.icon, 'pain-icon']"></i>
  34. <div class="pain-title pf-sc-semibold">{{ item.point }}</div>
  35. <div class="pain-desc pf-sc-regular">{{ item.pointDesc }}</div>
  36. </div>
  37. </div>
  38. </div>
  39. </section>
  40. <!-- Core Functions -->
  41. <section class="section-container bg-#F6F8FD">
  42. <div class="landing-container lt-sm:px-32px">
  43. <div class="section-title pf-sc-semibold">{{ solution.coreFunctions.title }}</div>
  44. <div class="grid grid-cols-1 sm:grid-cols-2 gap-24px mt-60px">
  45. <div
  46. v-for="(item, index) in solution.coreFunctions.function"
  47. :key="index"
  48. class="function-card"
  49. >
  50. <div class="function-card-header">
  51. <img src="~/assets/icons/function.svg" class="function-icon" alt="icon" />
  52. <div class="function-title pf-sc-semibold">{{ item.name }}</div>
  53. </div>
  54. <div class="function-desc pf-sc-regular">{{ item.funcDesc }}</div>
  55. <div class="function-tags">
  56. <span
  57. v-for="(tag, tIndex) in item.funcPoints"
  58. :key="tIndex"
  59. class="function-tag pf-sc-medium"
  60. >
  61. <i class="i-custom-check-one wh-15px lt-sm:wh-24px"></i>
  62. {{ tag }}
  63. </span>
  64. </div>
  65. </div>
  66. </div>
  67. </div>
  68. </section>
  69. <!-- Core Effects -->
  70. <section class="section-container bg-white">
  71. <div class="landing-container">
  72. <div class="section-title pf-sc-semibold">{{ solution.coreEffects.title }}</div>
  73. <div
  74. class="flex justify-between mt-80px gap-y-40px lt-sm:mt-72px lt-sm:grid lt-sm:grid-cols-2!"
  75. >
  76. <div
  77. v-for="(item, index) in solution.coreEffects.effect"
  78. :key="index"
  79. class="effect-item flex-1 text-center"
  80. >
  81. <div class="effect-percent d-din-pro-700-bold">{{ item.percent }}</div>
  82. <div class="effect-name pf-sc-regular">{{ item.name }}</div>
  83. </div>
  84. </div>
  85. </div>
  86. </section>
  87. <!-- Typical Cases -->
  88. <section v-if="currentCase" class="section-container solution-case">
  89. <div class="landing-container lt-sm:px-32px">
  90. <div class="section-title pf-sc-semibold">{{ solution.typicalCases.title }}</div>
  91. <div class="mt-60px lt-sm:mt-48px">
  92. <div class="case-card">
  93. <div class="case-img-wrapper">
  94. <img :src="currentCase.caseCover" class="case-img" :alt="currentCase.caseName" />
  95. </div>
  96. <div class="case-content">
  97. <div class="case-header">
  98. <div class="case-name pf-sc-semibold">{{ currentCase.caseName }}</div>
  99. <div v-if="(solution.typicalCases.cases?.length ?? 0) > 1" class="case-nav-btns">
  100. <!-- PC Prev -->
  101. <i
  102. class="i-custom-arrow-circle-left wh-32px transition-colors lt-sm:hidden"
  103. :class="
  104. activeCaseIndex === 0
  105. ? 'cursor-not-allowed opacity-50'
  106. : 'cursor-pointer hover:i-custom-arrow-circle-left-active'
  107. "
  108. @click="prevCase"
  109. ></i>
  110. <!-- Mobile Prev -->
  111. <i
  112. class="i-custom-button-previous-mobile wh-32px transition-all shrink-0 hidden lt-sm:block"
  113. :class="[
  114. activeCaseIndex === 0
  115. ? 'cursor-not-allowed i-custom-button-previous-mobile-disabled'
  116. : 'cursor-pointer active:i-custom-button-previous-mobile-active',
  117. ]"
  118. @click="prevCase"
  119. ></i>
  120. <!-- PC Next -->
  121. <i
  122. class="i-custom-arrow-circle-right wh-32px ml-12px transition-colors lt-sm:hidden"
  123. :class="
  124. activeCaseIndex === solution.typicalCases.cases.length - 1
  125. ? 'cursor-not-allowed opacity-50'
  126. : 'cursor-pointer hover:i-custom-arrow-circle-right-active'
  127. "
  128. @click="nextCase"
  129. ></i>
  130. <!-- Mobile Next -->
  131. <i
  132. class="i-custom-button-next-mobile wh-32px ml-12px transition-all shrink-0 hidden lt-sm:block"
  133. :class="[
  134. activeCaseIndex === solution.typicalCases.cases.length - 1
  135. ? 'cursor-not-allowed i-custom-button-next-mobile-disabled'
  136. : 'cursor-pointer active:i-custom-button-next-mobile-active',
  137. ]"
  138. @click="nextCase"
  139. ></i>
  140. </div>
  141. </div>
  142. <div class="case-section">
  143. <div class="case-subtitle pf-sc-semibold">客户痛点</div>
  144. <div class="case-text pf-sc-regular">{{ currentCase.casePainPoint }}</div>
  145. </div>
  146. <div
  147. class="case-section mt-24px bg-#F6F8FD p-16px rounded-8px lt-sm:mt-24px lt-sm:p-24px lt-sm:rounded-8px"
  148. >
  149. <div class="case-subtitle pf-sc-semibold">落地效果</div>
  150. <div class="case-text pf-sc-regular">
  151. <div v-for="(effect, eIndex) in currentCase.caseEffect" :key="eIndex">
  152. {{ effect }}
  153. </div>
  154. </div>
  155. </div>
  156. </div>
  157. </div>
  158. </div>
  159. </div>
  160. </section>
  161. <!-- CTA -->
  162. <SectionCta />
  163. </div>
  164. </template>
  165. <script setup lang="ts">
  166. import { solutionPoints } from '@/constants/common'
  167. const route = useRoute()
  168. const { openConsultation } = useConsultation()
  169. const solution = computed(() => solutionPoints.find((item) => item.id === route.params.id))
  170. const activeCaseIndex = ref(0)
  171. const currentCase = computed(() => {
  172. if (!solution.value?.typicalCases?.cases?.length) return null
  173. return solution.value.typicalCases.cases[activeCaseIndex.value]
  174. })
  175. const nextCase = () => {
  176. if (!solution.value?.typicalCases?.cases) return
  177. if (activeCaseIndex.value < solution.value.typicalCases.cases.length - 1) {
  178. activeCaseIndex.value++
  179. }
  180. }
  181. const prevCase = () => {
  182. if (activeCaseIndex.value > 0) {
  183. activeCaseIndex.value--
  184. }
  185. }
  186. </script>
  187. <style scoped lang="scss">
  188. .section-container {
  189. @apply py-120px lt-sm:py-120px;
  190. }
  191. .section-title {
  192. @apply font-s-36px text-#000000 text-center lh-60px mb-40px lt-sm:font-s-48px lt-sm:mb-40px;
  193. }
  194. /* Hero */
  195. .solution-hero {
  196. @apply w-full h-600px relative bg-cover bg-center bg-no-repeat;
  197. @apply lt-sm:h-auto lt-sm:py-160px;
  198. background-image: url('~/assets/images/solution-bg.png');
  199. @screen lt-sm {
  200. background-image: url('~/assets/images/solution-mobile-bg.png');
  201. }
  202. }
  203. .hero-title {
  204. @apply font-s-48px text-#000000;
  205. @apply lt-sm:font-s-56px lt-sm:px-20px;
  206. }
  207. .hero-subtitle {
  208. @apply font-s-38px text-#000000 mb-16px;
  209. @apply lt-sm:font-s-38px lt-sm:px-20px;
  210. }
  211. .hero-desc {
  212. @apply font-s-18px text-#091221/70;
  213. @apply lt-sm:font-s-26px lt-sm:px-32px;
  214. }
  215. .hero-btn {
  216. @apply w-162px h-56px rounded-8px mt-36px font-s-18px text-white hover:opacity-80 transition-colors cursor-pointer;
  217. @apply lt-sm:w-224px lt-sm:h-71px lt-sm:rounded-8px lt-sm:font-s-28px;
  218. }
  219. /* Pain Points */
  220. .pain-card {
  221. @apply bg-white rounded-16px p-40px border border-#E2E8F0 hover:shadow-md transition-all duration-300 text-center flex flex-col items-center;
  222. @apply lt-sm:rounded-16px lt-sm:p-40px;
  223. }
  224. .pain-icon {
  225. @apply wh-64px mb-16px;
  226. @apply lt-sm:wh-69px;
  227. }
  228. .pain-title {
  229. @apply font-s-18px text-#091221 mb-16px;
  230. @apply lt-sm:font-s-32px;
  231. }
  232. .pain-desc {
  233. @apply font-s-14px text-#091221/70 text-center;
  234. @apply lt-sm:font-s-24px;
  235. }
  236. /* Functions */
  237. .function-card {
  238. @apply bg-gradient-to-b from-[#E5E9F5] to-[#EFF2FB] rounded-16px p-40px transition-all duration-300;
  239. @apply lt-sm:rounded-16px lt-sm:p-40px;
  240. }
  241. .function-card-header {
  242. @apply flex items-start flex-col gap-12px mb-12px;
  243. @apply lt-sm:gap-24px lt-sm:mb-24px;
  244. }
  245. .function-icon {
  246. @apply wh-44px;
  247. @apply lt-sm:wh-78px;
  248. }
  249. .function-title {
  250. @apply font-s-18px text-#091221;
  251. @apply lt-sm:font-s-32px;
  252. }
  253. .function-desc {
  254. @apply font-s-14px text-#091221/70 mb-12px lh-24px;
  255. @apply lt-sm:font-s-24px lt-sm:lh-normal lt-sm:mb-24px;
  256. }
  257. .function-tags {
  258. @apply flex flex-wrap gap-26px;
  259. @apply lt-sm:gap-24px;
  260. }
  261. .function-tag {
  262. @apply flex items-center font-s-14px text-#091221 gap-8px;
  263. @apply lt-sm:font-s-24px lt-sm:gap-8px;
  264. }
  265. /* Effects */
  266. .effect-percent {
  267. @apply font-s-48px text-#0F67F8 lt-sm:font-s-56px;
  268. }
  269. .effect-name {
  270. @apply font-s-16px text-#384146;
  271. }
  272. .solution-case {
  273. @apply bg-cover bg-center bg-no-repeat;
  274. background-image: url('~/assets/images/case-bg.png');
  275. }
  276. /* Typical Cases */
  277. .case-card {
  278. @apply bg-white rounded-16px p-24px flex gap-40px items-stretch shadow-sm;
  279. @apply lt-sm:flex-col lt-sm:p-32px lt-sm:gap-42px lt-sm:rounded-16px;
  280. }
  281. .case-img-wrapper {
  282. @apply w-542px h-342px rounded-8px overflow-hidden flex-shrink-0;
  283. @apply lt-sm:w-full lt-sm:h-320px lt-sm:rounded-12px;
  284. }
  285. .case-img {
  286. @apply w-full h-full object-fill;
  287. }
  288. .case-content {
  289. @apply flex-1 py-10px flex flex-col justify-center;
  290. }
  291. .case-header {
  292. @apply flex justify-between items-center mb-30px;
  293. @apply lt-sm:mb-24px;
  294. }
  295. .case-name {
  296. @apply font-s-22px text-#091221;
  297. }
  298. .case-nav-btns {
  299. @apply flex items-center;
  300. }
  301. .case-subtitle {
  302. @apply font-s-18px text-#091221 mb-12px;
  303. @apply lt-sm:mb-12px;
  304. }
  305. .case-text {
  306. @apply font-s-14px text-#091221/70 lh-24px;
  307. @apply lt-sm:font-s-24px lt-sm:lh-normal;
  308. }
  309. </style>